#region References

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

#endregion

namespace Mss.Common.Helpers.SelectableCollection
{
    /// <summary>
    /// Collection which contains objects such can be selected.
    /// </summary>
    [Serializable]
    public class SelectableCollection<T> : ObservableCollection<T>, INotifyPropertyChanged where T : class, ISelectable

    {
        #region Events

        /// <summary>
        /// Occurs when a selected item changed.
        /// </summary>
        [field: NonSerialized]
        public event EventHandler SelectionChanged;

        /// <summary>
        /// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
        /// </summary>
        [field: NonSerialized]
        public override event NotifyCollectionChangedEventHandler CollectionChanged;

        [field: NonSerialized]
        private PropertyChangedEventHandler _propertyChangedEventHandler;

        [field: NonSerialized]
        public event EventHandler<EventArgs> Clearing;

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add
            {
                _propertyChangedEventHandler = Delegate.Combine(_propertyChangedEventHandler, value) as PropertyChangedEventHandler;
            }
            remove
            {
                _propertyChangedEventHandler = Delegate.Remove(_propertyChangedEventHandler, value) as PropertyChangedEventHandler;
            }
        } 

        #endregion

        #region Fields

        private readonly uint _capacity;
        private IEnumerator<ISelectable> _enumerator;
        private T _selectedItem;
        private ObservableCollection<T> _selectedItems;

        #endregion

        #region Constructor

        ///<summary>
        /// Default constructor.
        ///</summary>
        public SelectableCollection()
        {
            _capacity = uint.MaxValue;
        }
        ///<summary>
        /// Constructor with specified max capacity of collection.
        ///</summary>
        ///<param name="capacity">Collection capacity.</param>
        public SelectableCollection(uint capacity = uint.MaxValue)
        {
            _capacity = capacity;
        }

        ///<summary>
        /// Constructor with specified collection and max capacity of collection.
        ///</summary>
        ///<param name="enumerable">Base collection.</param>
        ///<param name="capacity">Collection capacity.</param>
        /// <exception cref="ArgumentNullException"><paramref name="enumerable" /> is <c>null</c>.</exception>
        public SelectableCollection(IEnumerable<T> enumerable, uint capacity = uint.MaxValue)
        {
            if (enumerable == null) throw new ArgumentNullException("enumerable");

            _capacity = capacity;

            foreach (var item in enumerable)
            {
                Add(item);
            }
        }

        #endregion

        #region Indexer
        public T this[int index]
        {
            get
            {
                return index >= Count ? null : base[index];
            }
        } 
        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets current Selected Item.
        /// </summary>
        public T SelectedItem
        {
            get { return this.Where(i => i.IsSelected).FirstOrDefault(); }
            set
            {
                if (_selectedItem == value) return;

                if (value == null)
                {
                    deselectItem(_selectedItem);
                }
                else
                {
                    selectItem(value);
                }

                _selectedItem = value;
                OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
            }
        }

        /// <summary>
        /// Gets or Sets collection of selected items.
        /// </summary>
        public ObservableCollection<T> SelectedItems
        {
            get { return _selectedItems; }
            set
            {
                if (_selectedItems == value) return;
                _selectedItems = value;

                if (_selectedItems != null)
                {
                    selectItems(_selectedItems);
                    _selectedItems.CollectionChanged += onSelectedItemsCollectionChanged;
                }

                OnPropertyChanged(new PropertyChangedEventArgs("SelectedItems"));
            }
        }

        #endregion

        #region Methods

        #region Public Methods

        protected virtual void OnClearing(EventArgs e)
        {
            if (Clearing != null)
                Clearing(this, e);
        }

        protected override void ClearItems()
        {
            OnClearing(EventArgs.Empty);
            base.ClearItems();
        }

        /// <summary>
        /// Method which allows to select next object in current collection.
        /// </summary>
        public void SelectNext()
        {
            //if (_enumerator == null)
            //    _enumerator = GetEnumerator();
            //try
            //{
            //    if (!_enumerator.MoveNext())
            //    {
            //        _enumerator.Reset();
            //        SelectNext();
            //        return;
            //    }
            //}

            //catch (InvalidOperationException)
            //{
            //    _enumerator = GetEnumerator();
            //}

            //if (_enumerator.Current == null)
            //{
            //        return;
            //}

            //_enumerator.Current.IsSelected = true;
        }

        /// <summary>
        /// Inserts an item into the collection at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which <paramref name="item"/> 
        /// should be inserted.</param><param name="item">The object to insert.</param>
        /// <exception cref="IndexOutOfRangeException">Collection is Full.</exception>
        protected override void InsertItem(int index, T item)
        {
            if (item == null) return;

            if (base.Count == _capacity)
                throw new IndexOutOfRangeException("Collection is Full.");
            base.InsertItem(index, item);
            item.SelectionChanged += onRoiControlSelectionChanged;
            selectItem(item);
        }

        /// <summary>
        /// Removes the item at the specified index of the collection.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        protected override void RemoveItem(int index)
        {
            SelectedItems.Remove(this[index]);

            base.RemoveItem(index);
        }

        public void RemoveSelectedItem()
        {
            Remove(SelectedItem);
        }

        public void RemoveSelectedItems()
        {
            if (SelectedItems == null || SelectedItems.Count == 0) return;

            var enumerator = SelectedItems.GetEnumerator();
            while (enumerator.MoveNext())
            {
                Remove(enumerator.Current);
                enumerator = SelectedItems.GetEnumerator();
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Raises the <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged"/> event with the provided arguments.
        /// </summary>
        /// <param name="e">Arguments of the event being raised.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler handler = CollectionChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.PropertyChanged"/> event with the provided arguments.
        /// </summary>
        /// <param name="e">Arguments of the event being raised.</param>
        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = _propertyChangedEventHandler;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void onSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    selectItem((T) e.NewItems[e.NewItems.Count - 1]);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    deselectItems(e.OldItems);
                    break;
            }

            _enumerator = GetEnumerator();
        }

        private void selectItems(IList<T> items)
        {
            foreach (T item in items)
            {
                selectItem(item);
            }
        }

        private void selectItem(T item, bool deselectOther = true, bool addToCollection = true)
        {
            item.IsSelected = true;

            if (deselectOther)
            {
                foreach (ISelectable selectable in Items)
                {
                    if (selectable == item) continue;
                    selectable.IsSelected = false;
                }

                if (SelectedItems != null)
                    SelectedItems.Clear();
            }

            if (addToCollection)
            {
                if (SelectedItems == null)
                    SelectedItems = new ObservableCollection<T>();

                SelectedItems.CollectionChanged -= onSelectedItemsCollectionChanged;

                if (SelectedItems.Contains(item))
                    SelectedItems.Remove(item);

                SelectedItems.Insert(0,item);
                
                SelectedItems.CollectionChanged += onSelectedItemsCollectionChanged;
            }

            OnPropertyChanged(new PropertyChangedEventArgs("SelectedItem"));
            if (SelectionChanged != null)
            {
                SelectionChanged.Invoke(this, EventArgs.Empty);
            }
        }

        private void deselectItems(IList items)
        {
            foreach (object item in items)
            {
                deselectItem((T) item);
            }
        }

        private void deselectItem(T item)
        {
            if (item.IsSelected)
            {
                item.IsSelected = false;
                return;
            }

            if (SelectedItem == item) SelectedItem = null;
            if (SelectedItems != null && SelectedItems.Contains(item)) SelectedItems.Remove(item);
        }

        private void onRoiControlSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var item = sender as T;
            if (item == null) return;

            if (e.AddToSelection)
            {
                selectItem(item,false,true);
                return;
            }

            if (!item.IsSelected)
                deselectItem(item);
            else
                selectItem(item);
        }

        #endregion

        #endregion
    }
}